<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

/**
 * Steps definitions for meta.
 *
 * @package   gradingform_metaagm
 * @category  test
 * @author    Nikita Kalinin <nixorv@gmail.com>
 * @copyright 2014 Oleg Sychev (Volgograd State Technical University) <oasychev@gmail.com>
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.

require_once(__DIR__ . '/../../../../../../lib/behat/behat_base.php');
//require_once(__DIR__ . '/../../../rubric/tests/behat/behat_gradingform_rubric.php');

use Behat\Behat\Context\Step\Given as Given;
use Behat\Behat\Context\Step\When;
use Behat\Gherkin\Node\TableNode;
use Behat\Mink\Exception\ExpectationException;

/**
 * Steps definitions for meta.
 *
 * @package   gradingform_metaagm
 * @category  test
 * @author    Nikita Kalinin <nixorv@gmail.com>
 * @copyright 2014 Oleg Sychev (Volgograd State Technical University) <oasychev@gmail.com>
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class behat_gradingform_metaagm extends behat_base {

    /**
     * Adds a new metacriterion to Meta form.
     *
     * @When /^I create the metacriterion "(?P<name_string>(?:[^"]|\\")*)" with grade weight "(?P<grade_weight_string>(?:[^"]|\\")*)" and grading method "(?P<method_name_string>(?:[^"]|\\")*)"$/
     * @param string $name
     * @param string $weight
     * @param string $method
     * @return Given[]
     */
    public function i_create_the_metacriterion_with_grade_weight_and_grading_method($name, $weight, $method) {
        return array(
            new Given('I click on "' . get_string('addmetacriterion', 'gradingform_metaagm') . '" "button"'),
            new Given('I set the field "' . get_string('metacriterionname', 'gradingform_metaagm') . '" to "' . $name . '"'),
            new Given('I set the field "' . get_string('metacriterionweight', 'gradingform_metaagm') . '" to "' . $weight . '"'),
            new Given('I set the field "' . get_string('gradingmethod', 'gradingform_metaagm') . '" to "' . $method . '"'),
            new Given('I press "' . get_string('savechanges') . '"')
        );
    }

    /**
     * @When /^I delete metacriterion "(?P<name_string>(?:[^"]|\\")*)"$/
     * @param $name
     * @return Given
     */
    public function i_delete_metacriterion($name) {
        $deletebtnxpath = $this->get_mc_form_input_xpath($name, get_string('deletemetacriterion', 'gradingform_metaagm'));
        $deletebtn = $this->find('xpath', $deletebtnxpath);
        $deletebtn->click();
        return new Given('I wait to be redirected');
    }

    /**
     * @When /^I rename metacriterion "(?P<old_name_string>(?:[^"]|\\")*)" to "(?P<new_name_string>(?:[^"]|\\")*)"$/
     * @param $oldname
     * @param $newname
     * @return Given[]
     * @throws \Behat\Mink\Exception\ElementException
     * @throws coding_exception
     */
    public function i_rename_metacriterion_to($oldname, $newname) {
        $mcsettingsbtnxpath = $this->get_mc_form_input_xpath($oldname, get_string('editsettings'));
        $mcsettingsbtn = $this->find('xpath', $mcsettingsbtnxpath);
        $mcsettingsbtn->click();
        return array(
            new Given('I set the field "' . get_string('metacriterionname', 'gradingform_metaagm') . '" to "' . $newname . '"'),
            new Given('I press "' . get_string('savechanges') . '"')
        );
    }

    /**
     * @When /^I change grade weight of metacriteria "(?P<name_string>(?:[^"]|\\")*)" to "(?P<grade_weight_string>(?:[^"]|\\")*)"$/
     * @param $mcname
     * @param $gradeweight
     * @return Given[]
     * @throws \Behat\Mink\Exception\ElementException
     * @throws coding_exception
     */
    public function i_change_grade_weight_of_metacriteria_to($mcname, $gradeweight) {
        $mcsettingsbtnxpath = $this->get_mc_form_input_xpath($mcname, get_string('editsettings'));
        $mcsettingsbtn = $this->find('xpath', $mcsettingsbtnxpath);
        $mcsettingsbtn->click();
        return array(
            new Given('I set the field "' . get_string('metacriterionweight', 'gradingform_metaagm') . '" to "' . $gradeweight . '"'),
            new Given('I press "' . get_string('savechanges') . '"')
        );
    }

    /**
     * @Given /^I create a definition for metacriterion "(?P<name_string>(?:[^"]|\\")*)"$/
     * @param $mcname
     */
    public function i_create_a_definition_for_metacriterion($mcname) {
        $mcsettingsbtnxpath = $this->get_mc_form_input_xpath($mcname, get_string('definenew', 'gradingform_metaagm'));
        $mcsettingsbtn = $this->find('xpath', $mcsettingsbtnxpath);
        $mcsettingsbtn->click();
    }

    /**
     * @Given /^I edit a definition for metacriterion "(?P<name_string>(?:[^"]|\\")*)"$/
     * @param $mcname
     */
    public function i_edit_a_definition_for_metacriterion($mcname) {
        $mcsettingsbtnxpath = $this->get_mc_form_input_xpath($mcname, get_string('editform', 'gradingform_metaagm'));
        $mcsettingsbtn = $this->find('xpath', $mcsettingsbtnxpath);
        $mcsettingsbtn->click();
    }

    /**
     * @When /^I change metacriterion "(?P<metacriterion_name_string>(?:[^"]|\\")*)" grading method to "(?P<method_name_string>(?:[^"]|\\")*)"$/
     * @param $mcname
     * @param $methodname
     * @return array
     * @throws \Behat\Mink\Exception\ElementException
     * @throws coding_exception
     */
    public function i_change_metacriterion_grading_method_to($mcname, $methodname) {
        $mcsettingsbtnxpath = $this->get_mc_form_input_xpath($mcname, get_string('editsettings'));
        $mcsettingsbtn = $this->find('xpath', $mcsettingsbtnxpath);
        $mcsettingsbtn->click();
        return array(
            new Given('I set the field "' . get_string('gradingmethod', 'gradingform_metaagm') . '" to "' . $methodname . '"'),
            new Given('I press "' . get_string('savechanges') . '"')
        );
    }

    /**
     * @When /^I delete the grading form of metacriterion "(?P<name_string>(?:[^"]|\\")*)"$/
     * @param $mcname
     * @throws \Behat\Mink\Exception\ElementException
     * @throws coding_exception
     */
    public function i_delete_the_grading_form_of_metacriterion($mcname) {
        $mcsettingsbtnxpath = $this->get_mc_form_input_xpath($mcname, get_string('deleteform', 'gradingform_metaagm'));
        $mcsettingsbtn = $this->find('xpath', $mcsettingsbtnxpath);
        $mcsettingsbtn->click();
    }

    /**
     * @When /^I share the grading form of metacriterion "(?P<name_string>(?:[^"]|\\")*)"$/
     * @param $mcname
     * @return Given
     * @throws \Behat\Mink\Exception\ElementException
     * @throws coding_exception
     */
    public function i_share_the_grading_form_of_metacriterion($mcname) {
        $mcsettingsbtnxpath = $this->get_mc_form_input_xpath($mcname, get_string('publishform', 'gradingform_metaagm'));
        $mcsettingsbtn = $this->find('xpath', $mcsettingsbtnxpath);
        $mcsettingsbtn->click();
        return new Given('I press "' . get_string('continue') . '"');
    }

    /**
     * @When /^I set metacriterion "(?P<metacriterion_name_string>(?:[^"]|\\")*)" to use "(?P<grading_form_template_string>(?:[^"]|\\")*)" grading form$/
     */
    public function i_set_metacriterion_to_use_grading_form($mcname, $templatename) {

        $templateliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($templatename);

        $templatexpath = "//h2[@class='template-name'][contains(., $templateliteral)]/" .
            "following-sibling::div[contains(concat(' ', normalize-space(@class), ' '), ' template-actions ')]";

        // Should work with both templates and own forms.
        $literaltemplate = $this->getSession()->getSelectorsHandler()->xpathLiteral(get_string('templatepick', 'grading'));
        $literalownform = $this->getSession()->getSelectorsHandler()->xpathLiteral(get_string('templatepickownform', 'grading'));
        $usetemplatexpath = "/a[./descendant::div[text()=$literaltemplate]]|" .
            "/a[./descendant::div[text()=$literalownform]]";

        $mcdefinefromtemplatebtnxpath = $this->get_mc_form_input_xpath($mcname, get_string('definefromtemplate', 'gradingform_metaagm'));
        $mcdefinefromtemplatebtn = $this->find('xpath', $mcdefinefromtemplatebtnxpath);
        $mcdefinefromtemplatebtn->click();

        return array(
            new Given('I set the field "' . get_string('searchownforms', 'grading') . '" to "1"'),
            new Given('I click on "' . get_string('search') . '" "button" in the "region-main" "region"'),
            new Given('I click on "' . $this->escape($usetemplatexpath) . '" "xpath_element" ' .
                'in the "' . $this->escape($templatexpath) . '" "xpath_element"'),
            new Given('I press "' . get_string('continue') . '"')
        );
    }


    /**
     * @Given /^I define the following marking guide:$/
     * @param TableNode $guide
     * @throws ExpectationException
     * @throws coding_exception
     */
    public function i_define_the_following_marking_guide(TableNode $guide) {

        $steptableinfo = '| criterion name | description for students | description for markers | max points |';

        $criteria = $guide->getRows();

        $addcriterionbutton = $this->find_button(get_string('addcriterion', 'gradingform_guide'));

        // Cleaning the current ones.
        $deletebuttons = $this->find_all('css', "input[title='" . get_string('criteriondelete', 'gradingform_guide') . "']");
        if ($deletebuttons) {

            // We should reverse the deletebuttons because otherwise once we delete
            // the first one the DOM will change and the [X] one will not exist anymore.
            $deletebuttons = array_reverse($deletebuttons, true);
            foreach ($deletebuttons as $buttonit => $button) {
                if ($buttonit > 0) {
                    $this->click_and_confirm($button);
                }
            }
        }

        if ($criteria) {
            foreach ($criteria as $criterionit => $criterion) {
                if (count($criterion) != 4) {
                    throw new ExpectationException('Follow this format: ' . $steptableinfo, $this->getSession());
                }

                // Add new criterion.
                if ($criterionit > 0) {
                    $addcriterionbutton->click();
                }

                $criterionroot = 'guide[criteria][NEWID' . ($criterionit + 1) . ']';

                $this->set_guide_field_value($criterionroot . '[shortname]', array_shift($criterion));
                $this->set_guide_field_value($criterionroot . '[description]', array_shift($criterion));
                $this->set_guide_field_value($criterionroot . '[descriptionmarkers]', array_shift($criterion));
                $this->set_guide_field_value($criterionroot . '[maxscore]', array_shift($criterion));
            }
        }
    }

    /**
     * Fills the first guide on page with the provided values.
     * @When /^I grade by filling the marking guide with:$/
     * @param TableNode $guide
     * @throws ExpectationException
     */
    public function i_grade_by_filling_the_marking_guide_with(TableNode $guide) {

        $criteria = $guide->getRowsHash();

        $stepusage = '"I grade by filling the marjing guide with:" step needs you to provide a table where each row is a criterion' .
            ' and each criterion has 3 different values: | Criterion name | Number of points | Remark text |';

        // To fill with the steps to execute.
        //$steps = array();

        $remarkfields = $this->find_all('xpath', "//div[starts-with(@id, 'guide-_metaagm_advancedgrading')]//textarea");
        $scorefields = $this->find_all('xpath', "//div[starts-with(@id, 'guide-_metaagm_advancedgrading')]//textarea/following::input[1]");

        $currentcriterion = 0;
        foreach ($criteria as $name => $criterion) {
            // We only expect the points and the remark, as the criterion name is $name.
            if (count($criterion) !== 2) {
                throw new ExpectationException($stepusage, $this->getSession());
            }

            // Numeric value here.
            $points = $criterion[0];
            if (!is_numeric($points)) {
                throw new ExpectationException($stepusage, $this->getSession());
            }

            $scorefields[$currentcriterion]->setValue($criterion[0]);
            $remarkfields[$currentcriterion]->setValue($criterion[1]);

            $currentcriterion++;
        }
    }

    protected function get_mc_form_input_xpath($formname, $elementname) {
        $formnameliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($formname);
        $elementnameliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($elementname);
        return "//*[self::a or self::legend][text()=" . $formnameliteral . "]/following::input[@value=" . $elementnameliteral . "][1]";
    }

    /**
     * Performs click confirming the action.
     *
     * @param NodeElement $node
     * @return void
     */
    protected function click_and_confirm($node) {

        // Clicks to perform the action.
        $node->click();

        // Confirms the delete.
        if ($this->running_javascript()) {
            $confirmbutton = $this->get_node_in_container(
                'button',
                get_string('yes'),
                'dialogue',
                get_string('confirmation', 'admin')
            );
            $confirmbutton->click();
        }
    }

    /**
     * Makes a hidden guide field visible (if necessary) and sets a value on it.
     *
     * @param string $name The name of the field
     * @param string $value The value to set
     * @param bool $visible
     * @return void
     */
    protected function set_guide_field_value($name, $value, $visible = false) {

        // Fields are hidden by default.
        if ($this->running_javascript() == true && $visible === false) {
            $xpath = "//*[@name='$name']/following-sibling::*[contains(concat(' ', normalize-space(@class), ' '), ' plainvalue ')]";
            $textnode = $this->find('xpath', $xpath);
            $textnode->click();
        }

        // Set the value now.
        $description = $this->find_field($name);
        $description->setValue($value);
    }
}